home *** CD-ROM | disk | FTP | other *** search
Text File | 1999-05-28 | 19.3 KB | 580 lines | [TEXT/CWIE] |
- // Unarchiver.java
- // By Ned Etcode
- // Copyright 1995, 1996, 1997 Netscape Communications Corp. All rights reserved.
-
- package netscape.util;
-
- import java.io.*;
- import netscape.application.Application;
-
- /** Object subclass implementing the Decoder interface to decode a graph of
- * objects from an Archive. The following example demonstrates how to use
- * an Unarchiver to retrieve the first root object from an Archive read from
- * System.in:
- *
- * <pre>
- * archive = new Archive();
- * archive.readASCII(System.in)
- * rootIdentifiers = archive.rootIdentifiers();
- * unarchiver = new Unarchiver(archive);
- * rootObject = unarchiver.unarchiveIdentifier(rootIdentifiers[0]);
- * </pre>
- *
- * @see Decoder
- * @see Archive
- * @see Archiver
- * @note 1.1 newInstance to protected
- * @note 1.1.1 use now Application.classForName if application available
- */
- public class Unarchiver implements Decoder {
- Archive archive;
- ArchivingStack stack = new ArchivingStack();
-
- Object objectForId[];
- boolean referenceGivenOut[];
-
- int unarchivedCount;
- Object unarchivedObjects[];
- ExternalCoder unarchivedCoders[];
-
- Object currentObject;
- ClassTable currentTable;
- int currentId;
- int currentColumnCount;
- int currentRow;
- int currentColumn;
-
- netscape.application.Application application;
- boolean applicationInitialized = false;
-
- /** Primitive constructor creating an Unarchiver that retrieves objects
- * from <b>archive</b>. Do not mutate the Archive while using the
- * Unarchiver.
- */
- public Unarchiver(Archive archive) {
- super();
-
- this.archive = archive;
- }
-
- /** Returns the archive from which the Unarchiver decodes objects.
- */
- public Archive archive() {
- return archive;
- }
-
- /** A convenience method for reading an object from a stream.
- * Equivilent to the code:
- * <pre>
- * archive = new Archive();
- * archive.read(in);
- * unarchiver = new Unarchiver(archive);
- *
- * rootIds = archive.rootIdentifiers();
- * if (rootIds == null || rootIds.length == 0)
- * return null;
- *
- * return unarchiver.unarchiveIdentifier(rootIds[0]);
- * </pre>
- */
- public static Object readObject(InputStream inputStream) throws
- IOException,
- CodingException {
- Archive archive;
- Unarchiver unarchiver;
- int rootIds[];
-
- archive = new Archive();
- archive.read(inputStream);
- unarchiver = new Unarchiver(archive);
-
- rootIds = archive.rootIdentifiers();
- if (rootIds == null || rootIds.length == 0)
- return null;
-
- return unarchiver.unarchiveIdentifier(rootIds[0]);
- }
-
- /** Unarchives a graph of objects starting from the object identified by
- * <b>identifier</b> in an archive. That object, and all objects it
- * references, will be reconstructed by calling their empty constructor
- * followed by <b>decode()</b>.
- * @see Archive#rootIdentifiers
- * @see Codable
- * @see Decoder
- */
- public Object unarchiveIdentifier(int identifier) throws CodingException {
- Object rootObject;
-
- if (identifier == 0)
- return null;
-
- // We make these arrays big enough to map all the ids in the archive.
-
- if (objectForId == null) {
- referenceGivenOut = new boolean[archive.identifierCount()];
- objectForId = new Object[referenceGivenOut.length];
- unarchivedObjects = new Object[referenceGivenOut.length];
- unarchivedCoders = new ExternalCoder[referenceGivenOut.length];
- }
-
- // Make sure that this is empty. It will be filled up by
- // objectForId() as it unarchives objects.
-
- clearFinishList();
-
- try {
- // Pull out the root object. This will cause all the objects it
- // references, and the ones they reference, etc. to be
- // unarchived.
-
- rootObject = objectForIdentifier(identifier);
-
- // Make a pass over all the newly unarchived objects and give them
- // a chance to do some post unarchiving clean up now that their
- // all their brethren have been created.
-
- processFinishList();
- } finally {
- // No matter what happens, be sure we clean up after ourselves.
-
- clearFinishList();
- }
-
- return rootObject;
- }
-
- // ALERT! This is the only dependency on application in util. There
- // should be a general solution to the Class.forName() problem with null
- // ClassLoaders.
-
- /** Called to look up a class from the Archive by name.
- */
- protected Class classForName(String className) throws CodingException {
- Class cls = null;
-
- if(!applicationInitialized) {
- application = Application.application();
- applicationInitialized = true;
- }
-
- try {
- if (application != null)
- cls = application.classForName(className);
- else
- cls = Class.forName(className);
- } catch (ClassNotFoundException e) {
- creationException(e.toString(), className);
- } catch (NoSuchMethodError e) {
- creationException(e.toString(), className);
- }
-
- return cls;
- }
-
- /** @private */
- protected Object newInstance(String className) throws CodingException {
- Class archivedClass;
- Object object = null;
-
- archivedClass = classForName(className);
-
- try {
- object = archivedClass.newInstance();
- } catch (InstantiationException e) {
- creationException(e.toString(), className);
- } catch (IllegalAccessException e) {
- creationException(e.toString(), className);
- } catch (NoSuchMethodError e) {
- creationException(e.toString(), className);
- }
-
- return object;
- }
-
- /** Whenever we have a problem making an object from the Archive, we
- * try to throw a helpful message.
- */
- private void creationException(String baseException, String className)
- throws CodingException {
- throw new CodingException(baseException + ". Class " + className +
- " must be public and define a constructor taking no arguments.");
- }
-
- // All ids > 0 map to non-null objects. id = 0 maps to null.
-
- private Object objectForIdentifier(int id) throws CodingException {
- ExternalCoder coder;
- Object object;
- ClassTable table;
- String className;
- ClassInfo info;
-
- // This works and won't go off the end of the array because we made
- // the objectForId array big enough to hold all the objects in the
- // archive.
-
- object = objectForId[id];
- if (object != null) {
- // If the object is not null, then we have given a reference out
- // to someone. We keep this state to detect replacement cycles.
-
- referenceGivenOut[id] = true;
- return object;
- } else if (id == 0)
- return null;
-
- // We haven't seen this id before, so we are going to have to make a
- // new instance.
-
- table = archive.classTableForIdentifier(id);
- className = table.className();
-
- // We always check to see if there is an external coder for a given
- // class name.
-
- coder = archive.externalCoderForName(className);
- if (coder != null) {
- object = coder.newInstance(className);
- } else {
- object = newInstance(className);
- }
-
- // To make unarchiving go fast, we stuff the ClassTable's fieldNames
- // with the exact Strings the class is using so that pointer
- // comparisons can be used to early-out equality tests.
-
- if (!table.hasUniqueStrings()) {
- info = new ClassInfo(className);
-
- if (coder != null)
- coder.describeClassInfo(object, info);
- else
- ((Codable)object).describeClassInfo(info);
-
- table.uniqueStrings(info);
- }
-
- // Put the original object in the list to get finishDecoding() later.
- // If the object was replaced, the new object should not get
- // finishDecoding() since it was not unarchived. The original object
- // can always forward finishDecoding() if appropriate.
-
- addToFinishList(coder, object);
-
- // This is where we decode the newly created object. Be careful to
- // read the object back out of the table in case it was replaced
- // during the call to decode().
-
- objectForId[id] = object;
- pushUnarchivingState(object, id);
-
- if (coder != null)
- coder.decode(object, this);
- else
- ((Codable)object).decode(this);
-
- popUnarchivingState();
- return objectForId[id];
- }
-
- /** This method pushes any current unarchiving state onto the stack and
- * sets up to begin unarchiving the given object. The pushed state
- * will be restored in popUnarchivingState().
- */
- private void pushUnarchivingState(Object object, int id) {
- stack.pushUnarchiver(this);
-
- currentObject = object;
- currentTable = archive.classTableForIdentifier(id);
- currentId = id;
- currentRow = archive.rowForIdentifier(id);
- currentColumn = -1;
- currentColumnCount = currentTable.fieldCount;
- }
-
- /** This method pops the stack of archiving states. The current object is
- * picked up where we left off.
- */
- private void popUnarchivingState() {
- stack.popUnarchiver(this);
- }
-
- /** This maintains the list of objects which have been unarchived in
- * this session.
- */
- private void addToFinishList(ExternalCoder coder, Object object) {
- unarchivedCoders[unarchivedCount] = coder;
- unarchivedObjects[unarchivedCount] = object;
- unarchivedCount++;
- }
-
- private void processFinishList() throws CodingException {
- int i, count;
- ExternalCoder coder;
- Object object;
-
- count = unarchivedCount;
- for (i = 0; i < count; i++) {
- coder = unarchivedCoders[i];
- object = unarchivedObjects[i];
-
- if (coder != null)
- coder.finishDecoding(object);
- else
- ((Codable)object).finishDecoding();
-
- // Clear things out while we're here. If an exception gets
- // thrown, clearFinishList() will be called anyway.
-
- unarchivedCoders[i] = null;
- unarchivedObjects[i] = null;
- }
-
- unarchivedCount = 0;
- }
-
- private void clearFinishList() {
- int i, count;
-
- count = unarchivedCount;
- for (i = 0; i < count; i++) {
- unarchivedCoders[i] = null;
- unarchivedObjects[i] = null;
- }
-
- unarchivedCount = 0;
- }
-
- /** This method is called at the beginning of each unarchive... method
- * to make sure that currentColumn matches the given key. In
- * general, the keys will be in the same order as the columns so the
- * pointer equality test will succeed and we'll rip right along.
- */
- private void prepareToUnarchiveField(String key) throws CodingException {
- int i, count;
- String fieldNames[];
-
- count = currentColumnCount;
- fieldNames = currentTable.fieldNames;
-
- // Scan forward looking for a matching column. It is kind of common
- // to omit fields when archiving/unarchiving, so skipping forward a
- // few after a miss will usually get us back on track.
-
- for (i = currentColumn + 1; i < count; i++) {
- if (key == fieldNames[i]) {
- currentColumn = i;
- return;
- }
- }
-
- // Our optimism has not paid off. Go ask the ClassTable for
- // the column.
-
- currentColumn = currentTable.columnForField(key);
- if (currentColumn < 0) {
- throw new CodingException("Unknown field name: " + key);
- }
- }
-
- /** Decoder interface method that returns the version information for the
- * class named <b>className</b>. Objects can use this information to
- * bring forward old encodings at runtime.
- */
- public int versionForClassName(String className) throws CodingException {
- return currentTable.versionForClassName(className);
- }
-
- /** Decoder interface method that decodes the boolean value associated
- * with the string <b>key</b>.
- */
- public boolean decodeBoolean(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.booleanAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the boolean array associated
- * with the string <b>key</b>.
- */
- public boolean[] decodeBooleanArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.booleanArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the character value associated
- * with the string <b>key</b>.
- */
- public char decodeChar(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.charAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the character array associated
- * with the string <b>key</b>.
- */
- public char[] decodeCharArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.charArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the byte value associated with
- * the string <b>key</b>.
- */
- public byte decodeByte(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.byteAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the byte array associated with
- * the string <b>key</b>.
- */
- public byte[] decodeByteArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.byteArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the short value associated with
- * the string <b>key</b>.
- */
- public short decodeShort(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.shortAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the short array associated with
- * the string <b>key</b>.
- */
- public short[] decodeShortArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.shortArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the integer value associated
- * with the string <b>key</b>.
- */
- public int decodeInt(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.intAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the integer array associated
- * with the string <b>key</b>.
- */
- public int[] decodeIntArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.intArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the long value associated with
- * the string <b>key</b>.
- */
- public long decodeLong(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.longAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the long array value associated
- * with the string <b>key</b>.
- */
- public long[] decodeLongArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.longArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the float value associated with
- * the string <b>key</b>.
- */
- public float decodeFloat(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.floatAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the float array associated with
- * the string <b>key</b>.
- */
- public float[] decodeFloatArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.floatArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the double value associated with
- * the string <b>key</b>.
- */
- public double decodeDouble(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.doubleAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the double array associated with
- * the string <b>key</b>.
- */
- public double[] decodeDoubleArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.doubleArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the string value associated with
- * the string <b>key</b>.
- */
- public String decodeString(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.stringAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes the string array associated with
- * the string <b>key</b>.
- */
- public String[] decodeStringArray(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return currentTable.stringArrayAt(currentRow, currentColumn);
- }
-
- /** Decoder interface method that decodes a reference to another Codable
- * object.
- */
- public Object decodeObject(String key) throws CodingException {
- prepareToUnarchiveField(key);
- return objectForIdentifier(currentTable.identifierAt(currentRow,
- currentColumn));
- }
-
- /** Decoder interface method that decodes an array of Codable objects. The
- * references to the Codable objects are shared, but the reference to the
- * array is not.
- */
- public Object[] decodeObjectArray(String key) throws CodingException {
- int i;
- int ids[];
- Object objects[];
-
- prepareToUnarchiveField(key);
- ids = currentTable.identifierArrayAt(currentRow, currentColumn);
- if (ids == null) {
- return null;
- }
-
- objects = new Object[ids.length];
-
- for (i = 0; i < ids.length; i++)
- objects[i] = objectForIdentifier(ids[i]);
-
- return objects;
- }
-
- /** Decoder interface method that replaces references to the object
- * currently being decoded with <b>replacement</b>. This method throws a
- * CodingException when an attempt is made to replace an object which has
- * already been seen by other objects. For maximum safety, this method
- * should only be called from leaves of the object graph.
- */
- public void replaceObject(Object replacement) throws CodingException {
- // Need to print out interesting debugging information here. ALERT!
- if (referenceGivenOut[currentId]) {
- throw new CodingException("Circular replacement exception");
- }
-
- objectForId[currentId] = replacement;
- }
- }
-